﻿using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;

/* Warning ! ! !
 * 如果Tick的时间间隔过小，可能会出现同一帧执行多次的情况
 * 比如一帧有10ms，那么就会执行10次间隔为1ms的Tick
 * 所以不能利用小间隔Tick来实现"下一帧执行"，如果有类似需求，可以考虑使用协程
 */

namespace Memo.Electricity.Engine
{
	public enum ETickType
	{
		Once,
		Loop
	};

	public abstract class CTick
	{
		public bool IsValid = true;
		public void Cancle()
		{
			CTickMgr.Inst?.DeregisterTick(this);
		}
	}

	public class CTickMgr : CSingletonBehaviour<CTickMgr>
	{

		#region 对外接口

		/// <summary>
		/// 注册一个计时器
		/// </summary>
		/// <param name="func">每次唤醒调用的回调函数</param>
		/// <param name="interval">唤醒的时间间隔</param>
		/// <param name="tickType">循环还是只运行一次</param>
		/// <returns></returns>
		public CTick RegisterTick(Action func, float interval, ETickType tickType = ETickType.Loop)
		{
			CUpdateTick uTick = new CUpdateTick()
			{
				mAwakeTime = Time.time + interval,
				mInterval = interval,
				mCallback = func,
				mTickType = tickType
			};
			mTicks.Add(uTick);
			return uTick;
		}

		public CTick RegisterOnce(Action func, float interval)
		{
			return RegisterTick(func, interval, ETickType.Once);
		}

		/// <summary>
		/// 取消一个计时器
		/// </summary>
		/// <param name="tick">要取消的计时器</param>
		public void DeregisterTick(CTick tick)
		{
			if (tick == null)
				return;
			tick.IsValid = false;
			CUpdateTick uTick = tick as CUpdateTick;
			if (mTicks.Contains(uTick))
			{
				mTicks.Remove(uTick);
			}
		}

		#endregion

		private class CUpdateTick : CTick, IComparable<CUpdateTick>
		{
			public ETickType mTickType;
			public float mInterval;
			public float mAwakeTime;
			public Action mCallback;

			public int CompareTo(CUpdateTick other)
			{
				int sign = Math.Sign(mAwakeTime - other.mAwakeTime);
				return sign == 0 ? Math.Sign(GetHashCode() - other.GetHashCode()) : sign;
			}
		}

		private SortedSet<CUpdateTick> mTicks;

		private void Awake()
		{
			mTicks = new SortedSet<CUpdateTick>();
		}

		private void Update()
		{
			CUpdateTick uTick = mTicks.Min;
			while (uTick != null && uTick.mAwakeTime <= Time.time)
			{
				mTicks.Remove(uTick);
				if (uTick.IsValid)
				{
					uTick.mCallback();
					switch (uTick.mTickType)
					{
						case ETickType.Loop:
							uTick.mAwakeTime = Time.time + uTick.mInterval;
							mTicks.Add(uTick);
							break;
						case ETickType.Once:
							uTick.IsValid = false;
							break;
					}
				}
				//找下一个Tick
				uTick = mTicks.Min;
			}
		}

	}
}